/*
 * Zehra Syed
 */

#include "queue.h"

// Initializes the queues and queue mutex.
void q_initialize() {
	int i;
	// Initialize each queue's mutex.
	queue_mutex = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t) * 6);
	for (i = 0; i < 6; i++) {
		int ret = pthread_mutex_init(&queue_mutex[i], NULL);
		if (ret)
			printf("Error while initializing mutex\n");
	}
	
	// Initialize all queues to "empty" (NULL)
	io_kb_queue     = NULL;
	io_disk_queue   = NULL;
	interrupt_queue = NULL;
}

// Get the first item of a queue.
Node * q_get(QUEUE queue_enum) {
	switch (queue_enum) {
	case IO_KEYBOARD_QUEUE:
		return io_kb_queue;
	case IO_DISK_QUEUE:
		return io_disk_queue;
	case IO_VIDEO_QUEUE:
		return io_video_queue;
	case INTERRUPT_QUEUE:
		return interrupt_queue;
	default:
		return NULL;
	}
}

// Change the first item of a queue.
void q_set_head(QUEUE queue_enum, Node * head) {
	switch (queue_enum) {
	case IO_KEYBOARD_QUEUE:
		io_kb_queue = head;
		break;
	case IO_DISK_QUEUE:
		io_disk_queue = head;
		break;
	case IO_VIDEO_QUEUE:
		io_video_queue = head;
		break;
		break;
	case INTERRUPT_QUEUE:
		interrupt_queue = head;
		break;
	default:
		break;
	}
}

// Acquires the index of a mutex
int q_get_mutex(QUEUE queue_enum) {
	switch (queue_enum) {
	case INTERRUPT_QUEUE:
		return 0;
	case IO_KEYBOARD_QUEUE:
		return 1;
	case IO_DISK_QUEUE:
		return 2;
	case IO_VIDEO_QUEUE:
		return 3;
	case SEM_BLOCKED_QUEUE:
		return 4;
	default:
		return -1;
	}
}

// Adds element to specified queue. Thread-safe.
void q_add(QUEUE queue_enum, int pid) {
	int ret = pthread_mutex_lock(&queue_mutex[q_get_mutex(queue_enum)]);
	if (ret)
		printf("Error while locking queue mutex\n");
	
	Node * node = (Node*) malloc(sizeof(Node));
	node->pid   = pid;
	node->next  = NULL;
	
	Node * current = q_get(queue_enum);
	if (current == NULL)
		// Queue is empty.
		q_set_head(queue_enum, node);
		
	else if (queue_enum == INTERRUPT_QUEUE && pid == -1) {
		node->next = current;
		q_set_head(INTERRUPT_QUEUE, node);
	} else {
		// Queue is not empty.
		while (current->next != NULL)
			current = current->next;
		current->next = node;
	}
	
	pthread_mutex_unlock(&queue_mutex[q_get_mutex(queue_enum)]);
}

// Removes and returns first element in queue. Thread-safe
int q_remove(QUEUE queue_enum) {
	int result;
	int ret = pthread_mutex_lock(&queue_mutex[q_get_mutex(queue_enum)]);
	if (ret)
		printf("Error while locking queue mutex\n");
	
	Node * head = q_get(queue_enum);
	if (head == NULL)
		result = -1;

	else {
		Node * tempNode = head; // Keep reference to head (so we don't lose it)
		q_set_head(queue_enum, head->next); // Set 'root' node to 'next'.
		int id = tempNode->pid; // Get PID from previous head.
		free(tempNode);         // Free previous head node.
		result = id;
	}
	
	pthread_mutex_unlock(&queue_mutex[q_get_mutex(queue_enum)]);
	
	return result;
}

// Prints queue. Thread-safe
void q_print(QUEUE queue_enum) {
	pthread_mutex_lock(&queue_mutex[q_get_mutex(queue_enum)]);
	
	Node * head = q_get(queue_enum);
	
	if (head == NULL)
		printf("[empty]\n");
	else
	{
		Node * current;
		current = head;
		while (current->next != NULL)
		{
			printf("%d ", current->pid);
			current = current->next;
		}
		printf("%d\n", current->pid);
	}
	
	pthread_mutex_unlock(&queue_mutex[q_get_mutex(queue_enum)]);
}

// Checks if a queue is empty.
// Returns '1' if queue is empty, '0' otherwise.
// Thread-safe.
int q_is_empty(QUEUE queue_enum) {
	int ret = pthread_mutex_lock(&queue_mutex[q_get_mutex(queue_enum)]);
	if (ret)
		printf("Error while locking queue mutex\n");
	
	int result = (q_get(queue_enum) == NULL);
	pthread_mutex_unlock(&queue_mutex[q_get_mutex(queue_enum)]);
	return result;
}
